<?php
namespace hid\storages\redis;

use hconnection\base\Connection;
use hconnection\ConnectionManager;
use hid\base\IdBuilder;
use hid\base\LocalStorage;
use Redis;
use Exception;

/**
 * redis 锁存储器
 *<B>说明：</B>
 *<pre>
 * 略
 *</pre>
 */
class RedisStorage extends LocalStorage
{
    /**
     * @var \Redis
     */
    protected $redis;

    protected $host;

    protected $port = '6379';

    protected $database = '0';

    protected $password = '';

    /**
     * 是否启用连接管理
     * @var bool
     */
    protected $onConnectManager = false;

    /**
     * 连接器类型
     * @var string
     */
    protected $connType = 'single';

    /**
     * 连接器对象
     * @var Connection
     */
    protected $connection;

    public function __construct(array $config = [])
    {
        parent::__construct($config);

        $this->initConnectionManager($config);
    }


    /**
     * 获取redsis对象
     *<B>说明：</B>
     *<pre>
     *  单例
     *</pre>
     * @return Redis
     */
    protected function getRedis():Redis
    {
        if (is_null($this->redis)) {
            $this->redis = $this->createRedis();
        }

        return $this->redis;
    }

    public function pingRedis(\Redis $redis):bool
    {
        var_dump('cache Redis ping:' . time());
        $response = $redis->ping();
        if (($response === true) || ($response === '+PONG') || ($response === 'PONG')) {
            return true;
        } else {
            return false;
        }
    }

    public function createRedis(array $config = []):Redis
    {
        $redis = new \Redis();
        if (!empty($config)) {
            $redis->connect($config['host'], $config['port']);
            $redis->auth($config['password']);
            $redis->select($config['database']);
        } else {
            $redis->connect($this->host, $this->port);
            $redis->auth($this->password);
            $redis->select($this->database);
        }

        return $redis;
    }

    protected function initConnectionManager(array $config)
    {
        if ($this->onConnectManager === true) {
            $this->connection = ConnectionManager::createConnection($this->connType,$config);
            $this->connection->setCreator([$this,'createRedis'])
                ->setHeartbeat([$this,'pingRedis']);
        }
    }

    protected function exec(callable $callback,string $key)
    {
        if ($this->onConnectManager ===  true) {
            return $this->connection->exec($callback,$key);
        } else {
            return $callback($this->getRedis());
        }
    }

    public function lock(string $name,int $tll = 0):bool
    {
        $script = <<<'LUA'
            local lockKey = KEYS[1]
            local content = ARGV[1]
            local ttl     = tonumber(ARGV[2])
            if ttl == 0 then
                return redis.call('setnx', lockKey, content)
            end
            
            local lockStatus = redis.call('set', lockKey, content, 'NX', 'PX', ttl)
            if lockStatus == 0 then
                local value = redis.call('get', lockKey)
                if(value == content) then
                    lockStatus = 1;
                    redis.call('pexpire', lockKey, ttl)
                end
            end
            return lockStatus
         LUA;

        return $this->exec(function(\Redis $redis) use($name,$tll,$script){
            return (bool)$redis->eval($script,[$name,bin2hex(random_bytes(8)),$tll * 1000],1);
        },$name);
    }

    public function unlock(string $name):bool
    {
        $script = <<<'LUA'
            local lockKey = KEYS[1]
            return redis.call('del', lockKey);
         LUA;

        return $this->exec(function(\Redis $redis) use($name,$script){
            return (bool)$redis->eval($script,[$name],1);
        },$name);
    }

    public function exists(string $name):bool
    {
        return $this->exec(function(\Redis $redis) use($name){
            return (bool)$redis->exists($name);
        },$name);
    }

    public function setValue(string $name,$value):bool
    {
        return $this->exec(function(\Redis $redis) use($name,$value){
            return (bool)$redis->set($name,$value);
        },$name);
    }

    public function getValue(string $name)
    {
        return $this->exec(function(\Redis $redis) use($name){
            return $redis->get($name);
        },$name);
    }

    public function incrBy(IdBuilder $idBuilder,int $step = 1):?int
    {
        return $this->exec(function(\Redis $redis) use($idBuilder,$step){
            $key = $idBuilder->getStoreKey();
            if ($idBuilder->isInitValue() && !$this->hasInitValue($key) && !$redis->exists($key)) {
                $redis->setnx($key,$idBuilder->getInitValueForStorage());
                $this->addInitValueCache($key);
            }
            return $redis->incrBy($key,$step);
        },$idBuilder->getStoreKey());
    }

}
